#include "wgloutputwindow.h"

#include <string>
#include <iostream>
#include <assert.h>
#include <shellapi.h>

#include "event.h"

using namespace std;

HWND                WglOutputWindow::m_Window = NULL; 
HDC                 WglOutputWindow::m_DeviceContext = NULL; 
HGLRC               WglOutputWindow::m_RenderContext = NULL;

deque<KeyEvent>     WglOutputWindow::m_EventQueue;
ResizeEvent         WglOutputWindow::m_ResizeEvent;
FiledropEvent       WglOutputWindow::m_FiledropEvent;
bool                WglOutputWindow::m_HasClosed = false;

WglOutputWindow::WglOutputWindow()
{
}

WglOutputWindow::~WglOutputWindow()
{
}

void WglOutputWindow::initialize(int width, int height, bool fullScreen)
{
    m_Width = width;
    m_Height = height;
    m_FullScreen = fullScreen;
    
    WNDCLASS   wndclass;
    HMODULE    hInstance = GetModuleHandle(NULL);
    RECT       windowRect;
    SetRect(&windowRect, 0, 0, m_Width, m_Height);
 
    wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 
	wndclass.lpfnWndProc   = (WNDPROC) WglOutputWindow::wndProc; 
    wndclass.cbClsExtra    = 0; 
    wndclass.cbWndExtra    = 0; 
    wndclass.hInstance     = hInstance; 
    wndclass.hIcon         = LoadIcon (NULL, IDI_WINLOGO); 
    wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW); 
    wndclass.hbrBackground = NULL; 
    wndclass.lpszMenuName  = NULL; 
    wndclass.lpszClassName = "Glover"; 
 
    if (!RegisterClass (&wndclass)) 
    {
        OutputDebugString("Failed to register window class");
        return; 
    }

    if (m_FullScreen)
    {
        DEVMODE dmScreenSettings;
        memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
        dmScreenSettings.dmSize = sizeof(dmScreenSettings);
        dmScreenSettings.dmPelsWidth	= m_Width;
        dmScreenSettings.dmPelsHeight	= m_Height;
        dmScreenSettings.dmBitsPerPel	= 32;
        dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

        if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
        {
            m_FullScreen = false;
            OutputDebugString("Failed to change display settings");
        }
    }
    
    unsigned long exStyle;
    unsigned long style;
    if (m_FullScreen)
    {
        exStyle = WS_EX_APPWINDOW;
        style = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
    }
    else
    {
        exStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
        style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
    }

    AdjustWindowRectEx(&windowRect, style, false, exStyle);
 
    m_Window = CreateWindowEx(exStyle, 
                              "Glover",
                              "Glover",
                              style,
                              0,
                              0,
                              m_Width, 
                              m_Height, 
                              NULL, 
                              NULL, 
                              hInstance, 
                              NULL); 
 
    if (!m_Window)
	{
        OutputDebugString("Failed to create window");
        destroy();
        return; 
	}

    m_DeviceContext = GetDC(m_Window); 
    setupPixelFormat();
    m_RenderContext = wglCreateContext(m_DeviceContext);
    wglMakeCurrent(m_DeviceContext, m_RenderContext); 
 
    DragAcceptFiles(m_Window, true);
    ShowWindow(m_Window, SW_SHOW);
    SetForegroundWindow(m_Window);
    SetFocus(m_Window);
}

void WglOutputWindow::destroy()
{
    if (m_FullScreen)
    {
        ChangeDisplaySettings(NULL, 0); //change back to desktop
    }

    if (m_RenderContext)
    {
        if (!wglMakeCurrent(NULL, NULL))
        {
            OutputDebugString("Failed to release opengl window");
        }
        if (!wglDeleteContext(m_RenderContext))
        {
            OutputDebugString("Failed to release render context");
        }
    }

    if (m_DeviceContext && !ReleaseDC(m_Window, m_DeviceContext))
    {
        OutputDebugString("Failed to release device context");
    }

    if (m_Window && !DestroyWindow(m_Window))
    {
        OutputDebugString("Failed to destroy the window");
    }

    if (!UnregisterClass("Glover", GetModuleHandle(NULL)))
    {
        OutputDebugString("Failed to unregister glover");
    }

    m_RenderContext = NULL;
    m_DeviceContext = NULL;
    m_Window = NULL;

}

void WglOutputWindow::setWindowTitle(const string& title)
{
    SetWindowText(m_Window, title.c_str());
}

void WglOutputWindow::setFullScreen(bool enabled)
{
    m_FullScreen = enabled;
    destroy();
    if (m_FullScreen)
    {
        m_ResizeEvent.width = GetSystemMetrics(SM_CXSCREEN); 
        m_ResizeEvent.height = GetSystemMetrics(SM_CYSCREEN); 
        initialize(m_ResizeEvent.width, m_ResizeEvent.height, m_FullScreen);        
    }
    else
    {
        initialize(m_Width, m_Height, m_FullScreen);    
        m_ResizeEvent.width = m_Width; 
        m_ResizeEvent.height = m_Height; 
    }
}

void WglOutputWindow::toggleVSync()
{
    m_VSync = !m_VSync;

    if (GLEE_WGL_EXT_swap_control)
    {
        wglSwapIntervalEXT(static_cast<int>(m_VSync));
    }
}

void WglOutputWindow::swapBuffers()
{
    SwapBuffers(m_DeviceContext);
}

bool WglOutputWindow::checkResizeEvent(ResizeEvent& event)
{
    bool gotEvent = false;

    if (m_ResizeEvent.height != 0 && m_ResizeEvent.height != 0)
    {
        gotEvent = true;
        event = m_ResizeEvent;
        m_ResizeEvent.reset();
    }

    return gotEvent;
}

bool WglOutputWindow::hasClosed()
{
    return m_HasClosed;
}

bool WglOutputWindow::checkFiledropEvent(FiledropEvent& event)
{
    if (!m_FiledropEvent.filenames.empty())
    {
        event = m_FiledropEvent;
        m_FiledropEvent.reset();
        return true;
    }

    return false;
}

bool WglOutputWindow::checkKeyEvent(KeyEvent& event)
{
    bool gotEvent = false;
    MSG  msg;

    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if (!m_EventQueue.empty())
    {
        gotEvent = true;
        event = m_EventQueue.front();
        m_EventQueue.pop_front();
    }
    
    return gotEvent;
}

bool WglOutputWindow::setupPixelFormat()
    {
    int pixelformat; 

    PIXELFORMATDESCRIPTOR pfd = {
        sizeof (PIXELFORMATDESCRIPTOR), /* struct size      */
        1,                              /* Version number   */
        PFD_DRAW_TO_WINDOW              /* Flags, draw to a window, */
        | PFD_DOUBLEBUFFER              /* Requires Doublebuffer hw */
        | PFD_SUPPORT_OPENGL,           /* use OpenGL               */
        PFD_TYPE_RGBA,                  /* RGBA pixel values        */
        32,                             /* 32-bit color             */
        0, 0, 0,                        /* RGB bits & shift sizes.  */
        0, 0, 0,                        /* Don't care about them    */
        0, 0,                           /* No alpha buffer info     */
        0, 0, 0, 0, 0,                  /* No accumulation buffer   */
        16,                             /* depth buffer             */
        0,                              /* stencil buffer           */
        0,                              /* No auxiliary buffers     */
        PFD_MAIN_PLANE,                 /* Layer type               */
        0,                              /* Reserved (must be 0)     */
        0,                              /* No layer mask            */
        0,                              /* No visible mask          */
        0                               /* No damage mask           */
    };
 
    if ( (pixelformat = ChoosePixelFormat(m_DeviceContext, &pfd)) == 0 ) 
    { 
        MessageBox(NULL, "ChoosePixelFormat failed", "Error", MB_OK); 
        return false; 
    } 
 
    if (SetPixelFormat(m_DeviceContext, pixelformat, &pfd) == FALSE) 
    { 
        MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK); 
        return false; 
    } 
 
    return true; 
}

LRESULT CALLBACK WglOutputWindow::wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT    ps; 
    KeyEvent event;

    switch(msg)
    {
    case WM_DROPFILES:
        {
            unsigned int filenameLength = DragQueryFile((HDROP)wParam, 0, 0, 0);
            char* filename = new char[filenameLength + 1];
            DragQueryFile(reinterpret_cast<HDROP>(wParam), 0, filename, filenameLength + 1);
            m_FiledropEvent.filenames.push_back(filename);
            DragFinish(reinterpret_cast<HDROP>(wParam));
            delete[] filename;
            break;
        }
    case WM_PAINT: 
        BeginPaint(hWnd, &ps); 
        EndPaint(hWnd, &ps);
        break; 
    case WM_CLOSE:
        m_HasClosed = true;
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
    case WM_SIZE: 
        m_ResizeEvent.width = LOWORD(lParam); 
        m_ResizeEvent.height = HIWORD(lParam); 
        break;
    case WM_CHAR:
        event.pressed = true;
        switch(static_cast<char>(wParam))
        {
        case 'a':
            event.keycode = KeyEvent::KEY_a;
            break;
        case 'f':
            event.keycode = KeyEvent::KEY_f;
            break;
        case 'h':
            event.keycode = KeyEvent::KEY_h;
            break;
        case 'q':
            event.keycode = KeyEvent::KEY_q;
            break;
        case 'r':
            event.keycode = KeyEvent::KEY_r;
            break;
        case 'v':
            event.keycode = KeyEvent::KEY_v;
            break;
        case 'x':
            event.keycode = KeyEvent::KEY_x;
            break;
        case 'y':
            event.keycode = KeyEvent::KEY_y;
            break;
        }
        m_EventQueue.push_back(event);
        break;
    case WM_KEYDOWN:
        event.pressed = true;

        switch(wParam)
        {
        case VK_LEFT:
            event.keycode = KeyEvent::KEY_LEFT;
            break;
        case VK_RIGHT:
            event.keycode = KeyEvent::KEY_RIGHT;
            break;
        case VK_UP:
            event.keycode = KeyEvent::KEY_UP;
            break;
        case VK_DOWN:
            event.keycode = KeyEvent::KEY_DOWN;
            break;
        case VK_SPACE:
            event.keycode = KeyEvent::KEY_SPACE;
            break;
        case VK_F1:
            event.keycode = KeyEvent::KEY_F1;
            break;
        case VK_F2:
            event.keycode = KeyEvent::KEY_F2;
            break;
        case VK_F3:
            event.keycode = KeyEvent::KEY_F3;
            break;
        case VK_F4:
            event.keycode = KeyEvent::KEY_F4;
            break;
        }
        m_EventQueue.push_back(event);
        break;
    /*case WM_SYSCOMMAND:
    {
        switch (wParam)
        {
        case SC_SCREENSAVE:
        case SC_MONITORPOWER:
            return 0;
        }
        break;
    }*/
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}
